AWS CDKを使ってアプリケーションをデプロイするワークショップに参加してきた #AWSreInvent

AWS CDKを使ってアプリケーションをデプロイするワークショップに参加してきた #AWSreInvent

re:Invent2024にて、AWS CDK(以下、CDK)を使ってアプリケーションをデプロイするワークショップに参加しましたので、こちらの内容と完走をダイジェストでまとめます。
Clock Icon2024.12.06

はじめに

おのやんです。

現在参加しているre:Invent2024にて、AWS CDK(以下、CDK)を使ってアプリケーションをデプロイするワークショップに参加しましたので、こちらの内容をダイジェストでまとめていきたいと思います。

セッション概要

[DOP304-R1] Develop AWS CDK resources to deploy your applications on AWS [REPEAT]

In this workshop, learn how to build and deploy applications using infrastructure as code with AWS Cloud Development Kit (AWS CDK). Create resources using AWS CDK, and learn maintenance and operations tips. In addition, get an introduction to building your own constructs.

[DOP304-R1] AWS CDKリソースを開発してアプリケーションをAWSにデプロイする [REPEAT]

このワークショップでは、AWS Cloud Development Kit (AWS CDK) を使用して、Infrastructure as Code を使用してアプリケーションを構築し、デプロイする方法を学びます。AWS CDKを使用してリソースを作成し、メンテナンスと運用のヒントを学びます。また、独自のコンストラクトを構築する方法を紹介します。

会場の様子

Mandalay Bayで開催されるGameDay・ワークショップに参加できなくなって時間が空き、空いた時間で何かセッションを受けれぬものかとVenetianを彷徨っていたら、ちょうど面白そうなワークショップを見つけました。あわよくば入れないかと、 試しにWalk-up列に40分くらい並んでみたら、予想外に席の空きが発生しているらしく参加してきました。

IMG_8148

今回は少しスライドから遠い位置になりましたが、席自体はど真ん中の最前列を陣取れました。

IMG_8126

セッション概要

今回は、Amazon API Gateway(以下、API Gateway)とAWS Lambda(以下、Lambda)、Amazon DynamoDB(以下、DynamoDB)をCKDで構築する内容になっていました。

IMG_8147

言語はPythonとTypeScriptを選ぶことができ,ワークショップ自体の構成として、環境構築・CDK入門・カスタムストラクチャ構築の3章に分かれている印象でした。ちなみに、私はTypeScriptでワークショップを進めました。

私自身CDKは初めて触ったので、本ワークショップで初めて触った感想をざっくりとまとめておきます。

  • Lambdaリソースを追加する時には、Lambdaのコンストラクトライブラリをインストールする必要がある。
  • CDKをデプロイする際には、自動で最適なIAMロールを設定してくれる
  • 例えば、Lambda関数にAPIを関連づける場合でも、以下のテンプレートから以下のCFnが生成される。API Gatewayに関わるリソースとかLambdaのパーミッションも暗黙的に追加される。
import { Stack, StackProps } from "aws-cdk-lib";
import { Code, Function, Runtime } from "aws-cdk-lib/aws-lambda";
import { Construct } from "constructs";
import { LambdaRestApi } from "aws-cdk-lib/aws-apigateway";

export class CdkWorkshopStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    // defines an AWS Lambda resource
    const hello = new Function(this, "HelloHandler", {
      runtime: Runtime.NODEJS_18_X, // execution environment
      code: Code.fromAsset("lambda"), // code loaded from "lambda" directory
      handler: "hello.handler", // file is "hello", function is "handler"
    });

    // defines an API Gateway REST API resource backed by our "hello" function.
    const gateway = new LambdaRestApi(this, "Endpoint", {
      handler: hello,
    });
  }
}

実際にコンソールを見てみると、こんな感じでCDKに明記していないリソースも追加されます。

start: Building 5f8b4b3220754e4306272556c6658e5cd67fae54c0505d1505b28876f547299d:current_account-current_region
success: Built 5f8b4b3220754e4306272556c6658e5cd67fae54c0505d1505b28876f547299d:current_account-current_region
start: Publishing 5f8b4b3220754e4306272556c6658e5cd67fae54c0505d1505b28876f547299d:current_account-current_region
success: Published 5f8b4b3220754e4306272556c6658e5cd67fae54c0505d1505b28876f547299d:current_account-current_region
Hold on while we create a read-only change set to get a diff with accurate replacement information (use --no-change-set to use a less accurate but faster template-only diff)
Stack CdkWorkshopStack
IAM Statement Changes
┌───┬─────────────────────┬────────┬───────────────────────┬────────────────────────────────────────────┬────────────────────────────────────────────┐
│   │ Resource            │ Effect │ Action                │ Principal                                  │ Condition                                  │
├───┼─────────────────────┼────────┼───────────────────────┼────────────────────────────────────────────┼────────────────────────────────────────────┤
│ + │ ${HelloHandler.Arn} │ Allow  │ lambda:InvokeFunction │ Service:apigateway.amazonaws.com           │ "ArnLike": {                               │
│   │                     │        │                       │                                            │   "AWS:SourceArn": "arn:${AWS::Partition}: │
│   │                     │        │                       │                                            │ execute-api:${AWS::Region}:${AWS::AccountI │
│   │                     │        │                       │                                            │ d}:${EndpointEEF1FD8F}/${Endpoint/Deployme │
│   │                     │        │                       │                                            │ ntStage.prod}/*/*"                         │
│   │                     │        │                       │                                            │ }                                          │
│ + │ ${HelloHandler.Arn} │ Allow  │ lambda:InvokeFunction │ Service:apigateway.amazonaws.com           │ "ArnLike": {                               │
│   │                     │        │                       │                                            │   "AWS:SourceArn": "arn:${AWS::Partition}: │
│   │                     │        │                       │                                            │ execute-api:${AWS::Region}:${AWS::AccountI │
│   │                     │        │                       │                                            │ d}:${EndpointEEF1FD8F}/test-invoke-stage/* │
│   │                     │        │                       │                                            │ /*"                                        │
│   │                     │        │                       │                                            │ }                                          │
│ + │ ${HelloHandler.Arn} │ Allow  │ lambda:InvokeFunction │ Service:apigateway.amazonaws.com           │ "ArnLike": {                               │
│   │                     │        │                       │                                            │   "AWS:SourceArn": "arn:${AWS::Partition}: │
│   │                     │        │                       │                                            │ execute-api:${AWS::Region}:${AWS::AccountI │
│   │                     │        │                       │                                            │ d}:${EndpointEEF1FD8F}/${Endpoint/Deployme │
│   │                     │        │                       │                                            │ ntStage.prod}/*/"                          │
│   │                     │        │                       │                                            │ }                                          │
│ + │ ${HelloHandler.Arn} │ Allow  │ lambda:InvokeFunction │ Service:apigateway.amazonaws.com           │ "ArnLike": {                               │
│   │                     │        │                       │                                            │   "AWS:SourceArn": "arn:${AWS::Partition}: │
│   │                     │        │                       │                                            │ execute-api:${AWS::Region}:${AWS::AccountI │
│   │                     │        │                       │                                            │ d}:${EndpointEEF1FD8F}/test-invoke-stage/* │
│   │                     │        │                       │                                            │ /"                                         │
│   │                     │        │                       │                                            │ }                                          │
└───┴─────────────────────┴────────┴───────────────────────┴────────────────────────────────────────────┴────────────────────────────────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Resources
[+] AWS::ApiGateway::RestApi Endpoint EndpointEEF1FD8F
[+] AWS::ApiGateway::Deployment Endpoint/Deployment EndpointDeployment318525DA5f8cdfe532107839d82cbce31f859259
[+] AWS::ApiGateway::Stage Endpoint/DeploymentStage.prod EndpointDeploymentStageprodB78BEEA0
[+] AWS::ApiGateway::Resource Endpoint/Default/{proxy+} Endpointproxy39E2174E
[+] AWS::Lambda::Permission Endpoint/Default/{proxy+}/ANY/ApiPermission.CdkWorkshopStackEndpoint018E8349.ANY..{proxy+} EndpointproxyANYApiPermissionCdkWorkshopStackEndpoint018E8349ANYproxy747DCA52
[+] AWS::Lambda::Permission Endpoint/Default/{proxy+}/ANY/ApiPermission.Test.CdkWorkshopStackEndpoint018E8349.ANY..{proxy+} EndpointproxyANYApiPermissionTestCdkWorkshopStackEndpoint018E8349ANYproxy41939001
[+] AWS::ApiGateway::Method Endpoint/Default/{proxy+}/ANY EndpointproxyANYC09721C5
[+] AWS::Lambda::Permission Endpoint/Default/ANY/ApiPermission.CdkWorkshopStackEndpoint018E8349.ANY.. EndpointANYApiPermissionCdkWorkshopStackEndpoint018E8349ANYE84BEB04
[+] AWS::Lambda::Permission Endpoint/Default/ANY/ApiPermission.Test.CdkWorkshopStackEndpoint018E8349.ANY.. EndpointANYApiPermissionTestCdkWorkshopStackEndpoint018E8349ANYB6CC1B64
[+] AWS::ApiGateway::Method Endpoint/Default/ANY EndpointANY485C938B

Outputs
[+] Output Endpoint/Endpoint Endpoint8024A810: {"Value":{"Fn::Join":["",["https://",{"Ref":"EndpointEEF1FD8F"},".execute-api.",{"Ref":"AWS::Region"},".",{"Ref":"AWS::URLSuffix"},"/",{"Ref":"EndpointDeploymentStageprodB78BEEA0"},"/"]]}}

ワークショップの内容自体はとてもシンプルで、最終的にこのようなコードを作成しました。

hitcounter.ts
import { AttributeType, Table } from "aws-cdk-lib/aws-dynamodb";
import { Code, Function, IFunction, Runtime } from "aws-cdk-lib/aws-lambda";
import { Construct } from "constructs";

export interface HitCounterProps {
  /** the function for which we want to count url hits **/
  downstream: IFunction;
}

export class HitCounter extends Construct {
  /** allows accessing the counter function */
  public readonly handler: Function;

  /** allows accessing the hit counter table */
  public readonly table: Table;

  constructor(scope: Construct, id: string, props: HitCounterProps) {
    super(scope, id);

    this.table = new Table(this, "Hits", {
      partitionKey: { name: "path", type: AttributeType.STRING },
    });

    this.handler = new Function(this, "HitCounterHandler", {
      runtime: Runtime.NODEJS_18_X,
      handler: "hitcounter.handler",
      code: Code.fromAsset("lambda"),
      environment: {
        DOWNSTREAM_FUNCTION_NAME: props.downstream.functionName,
        HITS_TABLE_NAME: this.table.tableName,
      },
    });

    // grant the lambda role read/write permissions to our table
    this.table.grantReadWriteData(this.handler);

    // grant the lambda role invoke permissions to the downstream function
    props.downstream.grantInvoke(this.handler);
  }
}
cdk-workshop-stack.ts
import { Stack, StackProps } from "aws-cdk-lib";
import { Code, Function, Runtime } from "aws-cdk-lib/aws-lambda";
import { Construct } from "constructs";
import { LambdaRestApi } from "aws-cdk-lib/aws-apigateway";
import { HitCounter } from "./hitcounter";
import { TableViewer } from "cdk-dynamo-table-viewer";

export class CdkWorkshopStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    // defines an AWS Lambda resource
    const hello = new Function(this, "HelloHandler", {
      runtime: Runtime.NODEJS_18_X, // execution environment
      code: Code.fromAsset("lambda"), // code loaded from "lambda" directory
      handler: "hello.handler", // file is "hello", function is "handler"
    });

    const helloWithCounter = new HitCounter(this, "HelloHitCounter", {
      downstream: hello,
    });

    // defines an API Gateway REST API resource backed by our "hello" function.
    const gateway = new LambdaRestApi(this, "Endpoint", {
      handler: helloWithCounter.handler,
    });

    const tv = new TableViewer(this, "ViewHitCounter", {
      title: "Hello Hits",
      table: helloWithCounter.table,
    });
  }
}

ワークショップを受けての所感

私自身IaCはAWS CloudFormation(以下、CFn)とTerraformを触っていて、今回のCDKもそれらと比較しながら進めていきました。一言で言うと、CFn・Terraformとは全く違うIaCだと感じました。

もともとプログラミング言語で構築できるのが特徴ですので、言語の理解が必須なのも大きいです。CFn・Terraformのテンプレートファイルがが設計図で、CDKは設計図の言語実装というイメージを持ちました。そのため、例えばIaCの選択肢をお客様などに提示する場合、何かしらの言語に精通しているという条件が加わって、初めてCDKを推奨できるような印象です。

本ワークショップ、CDKの入門にはすごくちょうどいいボリュームだったので、これからCDKを触るハードルが低くなりそうです。では!

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.